package afcp.alumni.service;

import java.util.LinkedList;
import java.util.List;
import java.util.Locale;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import afcp.alumni.dao.CsvDao;
import afcp.alumni.dao.MemberDao;
import afcp.alumni.dao.RoleDao;
import afcp.alumni.model.SearchResult;
import afcp.alumni.model.db.CsvColumn;
import afcp.alumni.model.db.Login;
import afcp.alumni.model.db.Member;
import afcp.alumni.model.db.Role;

/**
 * The Service class is called by the stripes action beans. It implements more
 * complex treatments of this application that souldn't remain in the action.
 * Only services should instantiate the DAOs and call its methods.
 * 
 * @author Antoine
 * 
 */
public class MemberService {
	
	MemberService() {}

	private static final Log logger = LogFactory.getLog(MemberService.class);

	private MemberDao memberDao = MemberDao.getDao();
	private RoleDao roleDao = RoleDao.getDao();
	private CsvDao csvDao = CsvDao.getDao();//TODO

	public static final String ROLE_ADMIN = "ADMIN";
	public static final String ROLE_ADMIN_LABEL = "Administrateur";
	public static final String ROLE_MEMBER = "MEMBER";
	public static final String ROLE_MEMBER_LABEL = "Membre";

	/**
	 * Returns a list of all the members registered in the application.
	 * 
	 * @return A list of all the members
	 */
	public List<Member> getAllMembers() {
		return memberDao.findAll();
	}
	/**
	 * 
	 * @param page page number, from 1 to the number of pages
	 * @return
	 */
	public SearchResult<Member> getMembersOfPage(int page, int perPage, Locale locale) {
		return memberDao.findOnePage(page, perPage, locale);
	}
	

	/**
	 * Adds a new member in the application or update it if it already exists in
	 * database. Consider to use {@link #memberExists(Member)} to check if it
	 * exists, or {@link #addMember(Member)} to do both in once.
	 * 
	 * @param member
	 *            The new member to add
	 * @return True if successfully added, false otherwise.
	 */
	public boolean addOrUpdateMember(Member member) {
		initMember(member);
		return memberDao.addOrUpdate(member);
	}
	
	private void initMember(Member member) {
		if (member.getLogin().getEntity() == null) {
			member.getLogin().setEntity(member);
		}
	}
	
	/**
	 * add the member if not already in database. otherwise do nothing.
	 * 
	 * @param member
	 * @return <code>true</code> if successfully added, <code>false</code> if
	 *         already exists or if a problem occurred.
	 */
	public boolean addMember(Member member) {
		initMember(member);
		if (memberExists(member)) {
			return false;
		}
		return memberDao.addOrUpdate(member);
	}
	
	public boolean updateMember(Member member) {
		initMember(member);
		if (!memberExists(member)) {
			return false;
		}
		return memberDao.update(member);
	}
	
	
	public Member getMember(Login login)
	{
		return memberDao.find(login);
	}
	
	public Member getMemberFromStringKey(String id)
	{
		return memberDao.find(id);
	}

	public boolean memberExists(Member member) {
		initMember(member);
		return memberDao.exists(member);
	}

	/**
	 * Removes the member with the given id from the application.
	 * 
	 * @param id
	 *            The id of the member to remove.
	 * @return True if successfully removed, false otherwise.
	 */
	public Member removeMemberFromKeyString(String id) {
		return memberDao.removeFromKeyString(id);
	}

	public SearchResult<Member> searchMembers(String queryString, int page, int perPage, Locale locale) {
		return memberDao.search(queryString, page, perPage, locale);
	}
	
	public boolean resetMembers() {
		Member admin = newMemberAdmin();
		boolean ok = memberDao.removeAll();
		ok &= memberDao.addOrUpdate(admin);
		return ok;
	}
	
	public Member addOrUpdateNewAdminMember() {
		Member admin = newMemberAdmin();
		boolean ok = memberDao.addOrUpdate(admin);
		if (ok) {
			return admin;
		} else {
			return null;
		}
	}
	
	public static final String ADMIN_LOGIN = "admin";
	
	private Member newMemberAdmin() {
		Member admin = new Member();
		admin.setLogin(new Login(ADMIN_LOGIN, admin));
//		admin.setId(ADMIN_LOGIN);
		admin.setFirstName("Administrateur");
		admin.setLastName("Administrateur");
		admin.setPassword("password");
		admin.setDeletable(false);
		admin.setRole(getRoleAdmin());
		return admin;
	}

	// Roles

	public Role getRole(String id) {
		return roleDao.find(id);
	}
	
	public List<Role> getAllRoles() {
		return roleDao.findAll();
	}
	public SearchResult<Role> getRolesOfPage(int page, int perPage, Locale locale) {
		return roleDao.findOnePage(page, perPage, locale);
	}
	
	private Role newRoleAdmin() {
		Role roleAdmin = new Role();
		roleAdmin.setDeletable(false);
		roleAdmin.setId(ROLE_ADMIN);
		roleAdmin.setLabel(ROLE_ADMIN_LABEL);
		return roleAdmin;
	}
	private Role roleAdmin = null;
	public Role getRoleAdmin() {
		if (roleAdmin == null) {
			roleAdmin = roleDao.find(ROLE_ADMIN);
			if (roleAdmin == null) {
				roleAdmin = newRoleAdmin();
				roleDao.addOrUpdate(roleAdmin);
			}
		}
		return roleAdmin;
	}
	
	private Role newRoleMember() {
		Role roleMember = new Role();
		roleMember.setDeletable(true);
		roleMember.setId(ROLE_MEMBER);
		roleMember.setLabel(ROLE_MEMBER_LABEL);
		return roleMember;
	}
	private Role roleMember = null;
	public Role getRoleMember() {
		if (roleMember == null) {
			roleMember = roleDao.find(ROLE_MEMBER);
			if (roleMember == null) {
				roleMember = newRoleMember();
				roleDao.addOrUpdate(roleMember);
			}
		}
		return roleMember;
	}

	/**
	 * Don't check if the role already exists in database. Use
	 * {@link #roleExists(Role)} to check if it exists, or
	 * {@link #addRole(Role)} to do both in once.
	 * 
	 * @param role
	 * @return
	 */
	public boolean addOrUpdateRole(Role role) {
		return roleDao.addOrUpdate(role);
	}
	
	/**
	 * Check if the role exists, and if not, add it in database. If found in
	 * database, do nothing and return false.
	 * 
	 * @param role
	 *            The role to add in database if it does not already exists.
	 * @return true if successfully added, false if it already exists in
	 *         database or if a problem occurred when trying to add the role in
	 *         database.
	 */
	public boolean addRole(Role role) {
		if (roleExists(role)) {
			return false;
		}
		return roleDao.addOrUpdate(role);
	}

	public boolean roleExists(Role role) {
		return roleDao.exists(role);
	}

	public boolean roleExists(Member member) {
		Role role = roleDao.find(member.getRoles().get(0).getId());
		boolean result = role != null;
		if (result) {
			member.getRoles().set(0, role);
		}
		return result;
	}
	
	public Role removeRole(String id) {
		return roleDao.remove(id);
	}

	public boolean resetRoles() {
		List<Role> oldRoles = roleDao.findAll();
		List<Role> rolesToKeep = new LinkedList<>();

		for (Role role : oldRoles) {
			if (!role.isDeletable() || ROLE_ADMIN.equals(role.getId())) {
				rolesToKeep.add(new Role(role));
				// Or delete from a list of roles to delete, and call
				// roleDao.remove(List) instead of removeAll().
			}
		}
		if (rolesToKeep.isEmpty()) {
			rolesToKeep.add(newRoleAdmin());
			logger.info("No role to keep, creating admin role.");
		}

//		boolean ok = roleDao.remove(oldRoles);
		boolean ok = roleDao.removeAll();
		if (ok) {
			logger.info("Adds the new roles");
			ok &= roleDao.addOrUpdate(rolesToKeep);
		} else {
			logger.info("A problem occurred while deleting the roles. Won't add the new roles.");
		}
		return ok;
	}
	
	public boolean removeRoles() {
		return roleDao.removeAll();
	}
	
	public boolean addDefaultRoles() {
		List<Role> defaultRoles = new LinkedList<>();
		Role newRole = new Role();
		newRole.setDeletable(false);
		newRole.setId(ROLE_ADMIN);
		newRole.setLabel("Administrateur");
		defaultRoles.add(newRole);
		return roleDao.addOrUpdate(defaultRoles);
	}
	
	public boolean rebuildIndex() {
		return memberDao.clearIndex() & memberDao.addToIndex(this.getAllMembers());
	}
	
	private List<CsvColumn> columns = null;
	public List<CsvColumn> getAllCsvColumns() {
		if (columns == null) {
			columns = csvDao.findAll();
			if (columns.isEmpty()) {
				logger.info("No CSV Column in database. Initialize column list with default columns");
				for (CsvColumn column : Member.defaultColumns) {
					columns.add(column);
				}
				csvDao.addOrUpdate(columns);
			}
		}
		return columns;
	}

}
